﻿using NAudio.Wave;
using SoundEngine3;
using System;
using System.Collections.Generic;
using Zquence_Studio_3_WindowsDesktop.SoundEngine.Classes;
using SoundEngine3.AudioProcessing;
using SoundEngine3.DataTypes;
using SoundEngine3.DspPlugins.Meta;
using static SoundEngine3.AudioProcessing.CommonRenderer;
using System.Reflection;

namespace Zquence_Studio_3_WindowsDesktop.SoundEngine
{
    /// <summary>
    /// Reference: https://cscore.codeplex.com/SourceControl/latest#CSCore/SoundOut/DirectSoundOut.cs
    /// </summary>
    public class DirectSoundAdapter : WasapiDirectSoundShared
    {
        private DirectSoundOut mDirectSound;
        private Guid mCurrentDevice;
        
        private EnumParameter<MillisecondsLatency> bufferSizeProperty;
        private EnumParameter<SampleRates.Value> sampleRateProperty;
        private int mBufferSize;
        private int mSampleRate = 44100;

        public int Latency = 40;

        public override int MinimumBufferSize
        {
            get { return PreferredBufferSize; }
        }

        public override int MaximumBufferSize
        {
            get { return PreferredBufferSize; }
        }

        public override int PreferredBufferSize
        {
            get { return mBufferSize; }
        }

        public override double SampleRate
        {
            get { return mSampleRate; }
        }

        public override int InputChannels
        {
            get { return 0; }
        }

        public override int OutputChannels
        {
            get { return 2; }
        }

        public override bool Started
        {
            get
            {
                return mDirectSound.PlaybackState == PlaybackState.Playing;
            }
        }

        public DirectSoundAdapter()
            : base("DirectSound")
        {
            // Get all direct sound devices.
            IEnumerable<DirectSoundDeviceInfo> dsdevices = NAudio.Wave.DirectSoundOut.Devices;
            
            foreach (var dsdevice in dsdevices)
            {
                SoundDevice device = new SoundDevice(dsdevice.Description, this, dsdevice.Guid);
                AddSoundDevice(device);
            }
        }

        public override void Initialize(int bufferSize, int sampleRate)
        {
            // TODO: Now to set latency ms?
            mSampleRate = sampleRate;
        }

        public override void SetSoundDevice(SoundEngine3.SoundDevice soundDevice)
        {
            if (mDirectSound != null)
            {
                Destroy();
                mDirectSound.Dispose();
            }

            if (soundDevice != null)
            {
                mCurrentDevice = soundDevice.GetDeviceIdentifier<Guid>();

                SetupWasapi(Latency, mSampleRate);
            }
        }

        protected override bool OnInit()
        {
            lock (SoundEngine3.SoundEngine.SoundLocker)
            {
                // HACK: To get buffer size info.
                var audioBufferSize = typeof(DirectSoundOut).GetField("samplesFrameSize", BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Instance);
                mBufferSize = (int)audioBufferSize.GetValue(mDirectSound) / OutputChannels;
            }

            if (mBufferSize != 0)
            {
                if (!mRequestStop)
                {
                    SetupBuffers();
                    AudioBufferProcessor.Start();
                }
                return true;
            }

            return false;
        }

        bool mRequestStop;

        public override void Start()
        {
            mRequestStop = false;
            mDirectSound.Play();
        }

        public override void Stop()
        {
            mRequestStop = true;
            if (IsInitialized)
            {
                AudioBufferProcessor.Stop();
            }
            mDirectSound.Stop();

            Destroy();
        }
        
        public override void Dispose()
        {
            Destroy();
            if (mDirectSound != null)
            {
                mDirectSound.Dispose();
            }
        }

        protected override void FeatchInputSamples(Channel inputChannel)
        {
            throw new NotImplementedException();
        }

        public void SetupWasapi(int latency, int sampleRate)
        {
            mSampleRate = sampleRate;

            mDirectSound = new DirectSoundOut(mCurrentDevice, latency);

            WaveProvider32 mCurrentBufferedProvider = new WaveProvider32(this, (int)SampleRate, 2);
            mDirectSound.Init(mCurrentBufferedProvider);
        }

        private void SetupBuffers()
        {
            AudioInput.SetChannels(InputChannels, PreferredBufferSize);
            RaiseOnInputChannelChanged(OutputChannels);

            AudioOutput.SetChannels(OutputChannels, PreferredBufferSize);
            RaiseOnOutputChannelChanged(OutputChannels);

            RaiseOnBufferSizeChanged(PreferredBufferSize, PreferredBufferSize, PreferredBufferSize);

            AudioInput.SampleRate = SampleRate;
            AudioOutput.SampleRate = SampleRate;
            RaiseOnSampleRateChanged(SampleRate);

            Config(OutputChannels, PreferredBufferSize);
        }

        public override void GetGUIControls(List<ParameterProperties> uiControls)
        {
            // Buffer size.
            bufferSizeProperty = new EnumParameter<MillisecondsLatency>(null);
            bufferSizeProperty.EnumValue = (MillisecondsLatency)Latency;
            bufferSizeProperty.EnumMinValue = MillisecondsLatency._30;
            bufferSizeProperty.OnValueChanged += delegate (ParameterProperties value)
            {
                Stop();
                Dispose();
                lock (SoundEngine3.SoundEngine.SoundLocker)
                {
                    Latency = (int)bufferSizeProperty.EnumValue;
                    SetupWasapi(Latency, mSampleRate);
                    Start();
                }
            };
            bufferSizeProperty.Label = "Latency ms";
            uiControls.Add(bufferSizeProperty);

            // TODO: Nu verkar vi få ihop Wasapi, DirectSound och OpenAL rätt bra, DirectSound verkar få problem och crash när vi kör >30 och Wasapi klarar inte av 10 ms. Kolla på det.
            
            // Sample rate.
            sampleRateProperty = new EnumParameter<SampleRates.Value>(null);
            sampleRateProperty.EnumValue = (SampleRates.Value)mSampleRate;
            sampleRateProperty.OnValueChanged += delegate (ParameterProperties value)
            {
                Stop();
                Dispose();
                lock (SoundEngine3.SoundEngine.SoundLocker)
                {
                    mSampleRate = (int)sampleRateProperty.EnumValue;
                    SetupWasapi(Latency, mSampleRate);
                    Start();
                }
            };
            sampleRateProperty.Label = "Sample rate";
            uiControls.Add(sampleRateProperty);
        }
    }
}
